
#ifndef __RSP_S_ROOM__HEADER_FILE__
#define __RSP_S_ROOM__HEADER_FILE__

#include "coid/comm/binstream/netstreamudpbuf.h"

#include "s_controller.h"
#include "s_connector.h"
#include "s_object.h"
#include "viewcont.h"
#include "classdef.h"

using namespace coid;


////////////////////////////////////////////////////////////////////////////////
struct RoomSlot
{
    uint16 id;

    ushort get_room() const             { return id >> 6; }
    ushort get_slot() const             { return id & 0x3f; }

    void set( ushort room, ushort slot )
    {
        id = (room<<6) | slot;
    }

    friend binstream& operator << (binstream& bin, RoomSlot id)
    {   bin << id.id;  return bin; }

    friend binstream& operator >> (binstream& bin, RoomSlot& id)
    {   bin >> id.id;  return bin; }
};


////////////////////////////////////////////////////////////////////////////////
///Server side object rsp_s_object manager
struct rsp_s_room_controller : rsp_controller
{
    rsp_s_room_controller() : rsp_controller(CT_SERVER_ROOM)
    {
        _nplactive = 0;
        _active_connector = 0;
    }

    void view_add( rsp_base* p )
    {
        _vc.view_ins(p);
    }

    rsp_s_object_state* view_find( rsp_base* p ) const  { return _vc.view_find(p); }
    void view_del( rsp_base* p )                    { _vc.view_del(p); }


    ///Ask if the attach request can be processed
    virtual opcd allow_attach( const netAddress& addr ) const = 0;


    ///Attach room
    virtual opcd attach_room() = 0;

    ///Attach replicated objects to instance of server controller
    virtual opcd attach_client( rsp_s_connector& rsps ) = 0;

    ///Detach object from instance of server controller
    virtual bool detach_client( rsp_s_connector& rsps ) = 0;
    
   

    opcd attach_connector( uint32 ssid, netstreamudpbuf& net, const netAddress& addr, const token& nick, uint triptime, ushort* slot )
    {
        Gactive_controller = this;

        //check if this is not a reconnect
        uint sn = _ctrls.size();
        uint ff = UMAX; //first free
        for( uint i=0; i<sn; ++i )
        {
            if( !_ctrls[i]._ctrl.is_set() )
            {
                if( ff == UMAX )
                    ff=i;
            }
            else if( _ctrls[i]._ctrl->get_ssid() != 0 )
            {
                if( _ctrls[i]._ctrl->get_nick() == nick )
                    return ersALREADY_EXISTS "same nick";
            }
            else if( _ctrls[i]._ctrl->get_nick() == nick )
            {
                sn = i;
                break;
            }
        }

        if( sn >= _ctrls.size() && ff != UMAX )
            sn = ff;

        local<rsp_s_connector> lctrl;
        rsp_s_connector* ctrl;

        if( sn >= _ctrls.size() || !_ctrls[sn]._ctrl.is_set() )
            ctrl = lctrl = new rsp_s_connector( _vc, sn, nick );
        else
            ctrl = _ctrls[sn]._ctrl;

        ctrl->reset();
        ctrl->init_ssid(ssid);

        opcd e;
        SERVER_ONLY_BEGIN
        e = attach_client( *ctrl );
        SERVER_ONLY_END

        if(!e)
        {
            ++_nplactive;
            Slot* sl;
            
            if( sn < _ctrls.size() )
                sl = &_ctrls[sn];
            else
                sl = _ctrls.add();

            if( lctrl.is_set() )
                sl->_ctrl = lctrl.eject();

            sl->_net.set_request_compression(true);
            sl->_net.set_socket( net.get_socket(), true );
            sl->_net.set_remote_address(addr);
            sl->_net.set_packet_size( net.get_packet_size() );
            sl->_trip_time = triptime;

            *slot = (ushort)sn;
        }

        return e;
    }

    ///Detach client
    bool detach_connector( uint c, bool force=false )
    {
        Gactive_controller = this;

        Slot& sl = _ctrls[c];

        //bin.set_remote_address( sl._addr );
        sl._net.reset();
        sl._net << (uint8)RSP_PACKET::DISCONNECT << BINSTREAM_FLUSH;

        LOG(0) {
            if(force) *TLOG << "FORCED ";
            *TLOG << "DETACHING \"" << sl._ctrl->get_nick() << "\" from " << *sl._net.get_address()
            << " slot: " << c << ", ssid: " << sl._ctrl->get_ssid() << " at " << timefmt() << BINSTREAM_FLUSH;
        }

        bool ds = detach_client( *sl._ctrl );
        if( !force && ds )
        {
            //keep the slot for reconnects
            sl._ctrl->reset();
        }
        else
        {
            sl._ctrl.destroy();
            uint n = _ctrls.size();
            //discard trailing elements
            if( c == n-1 )
            {
                for( ; n>0; )
                {
                    --n;
                    if( _ctrls[n]._ctrl.is_set() )  { ++n; break; }
                }
                _ctrls.need(n);
            }
        }

        --_nplactive;
        return ds;
    }


    void request_detach_all_connectors()
    {
        _reqdetachall = true;
    }


    int accept_client_data( netstreamudpbuf& bin, ushort slot, uint32 ssid )
    {
        if( slot >= _ctrls.size() )
            return 0;   //invalid slot

        Slot& s = _ctrls[slot];
        if( !s._ctrl.is_set() )
            return 0;   //a disconnected slot

        if( s._ctrl->get_ssid() != ssid )
            return 0;  //invalid session id

        //allow port number change
        ushort port = bin.get_port();
        if( port != s._net.get_port() )
            s._net.set_port(port);

        if( *s._net.get_address() != *bin.get_address() )
            return 0;   //a hijack attempt?

        //ok, remaining stuff is received by the connector
        return s._ctrl->patch(bin);
    }

    bool accept_client_disconnect( netstreamudpbuf& bin, ushort slot, uint32 ssid )
    {
        if( slot >= _ctrls.size() )
            return false;  //invalid slot

        if( !_ctrls[slot]._ctrl.is_set() )
            return false;   //a disconnected slot

        if( _ctrls[slot]._ctrl->get_ssid() != ssid )
            return false;   //invalid session id

        return detach_connector(slot);
    }

    void process_frame( uint stateid )
    {
        Gactive_controller = this;
        _stateid = stateid;

        uint n = _vc._views.size();
        {for( uint i=0; i<n; ++i )
            if( _vc._views[i] )
                _vc._views[i]->frame();
        }

        {for( uint i=0; i<n; ++i )
            if( _vc._views[i] )
                _vc._views[i]->frame_end();
        }

        //connectors at last
        //for( uint c=0; c<_ctrls.size(); ++c )
        //    if( _ctrls[c]._ctrl.is_set()  &&  _ctrls[c]._ctrl->get_ssid() )  _ctrls[c]._ctrl->process_frame();
    }

    void process_diffs()
    {
        Gactive_controller = this;

        if(_reqdetachall)
        {
            uint n = _ctrls.size();
            for( uint i=0; i<n; ++i )
                if( _ctrls[i]._ctrl.is_set() )
                {
                    if( _ctrls[i]._ctrl->get_ssid() != 0 )
                        detach_connector( i, true );
                    else
                        _ctrls[i]._ctrl.destroy();
                }
            _ctrls.reset();

            _nplactive = 0;
            _reqdetachall = false;
            return;
        }

        //compute diffs for all connected controllers
        // diffs are computed for the latest state id only
        uints n = _ctrls.size();
        for( uint i=0; i<n; ++i )
        {
            _active_connector = _ctrls[i]._ctrl;;
            rsp_s_connector* ctrl = _active_connector;

            if(!ctrl)  continue;
            if( ctrl->get_ssid() == 0 )  continue;   //disconnected

            _ctrls[i]._limit += RSPS_SETTINGS._maxpacketperframe;
            if( _ctrls[i]._limit > 0 )
                _ctrls[i]._limit = 0;

            netstreamudpbuf& bin = _ctrls[i]._net;

            if( ctrl->is_dead() )
            {
                detach_connector(i);
                continue;
            }
/*
            //skip the diff step if instructed to do so
            if( _ctrls[i]._waitframes > 0 ) {
                --_ctrls[i]._waitframes;
                continue;
            }
*/
            if( _ctrls[i]._limit < 0 )
                continue;

            if( bin.needs_send() )
            {
                uints sz = (uints)bin.get_size();

                //packets from previous diff pending
                _ctrls[i]._limit -= bin.send( RSPS_SETTINGS._maxpacketsize );

                if( !bin.needs_send() )
                {
                    uint asz = bin.get_sendsize();

                    LOG(1) {
                        *TLOG << timefmt(RSP_SETTINGS.active_frame()) << " multipart datagram sent: "
                        << sz << " bytes (actual: " << asz << " bytes, "
                        << (asz*100/sz) << "% compression)" << BINSTREAM_FLUSH;
                    }
                }

                continue;
            }

            DASSERT( bin.get_size() == 0 );

            /*bool nonempty = */
            ctrl->diff(&bin);
            {
                uints mss = (uints)bin.get_size();
                //DASSERT( mss <= 15  ||  0 != *(ushort*)bin.dbg_get_data(13) );

                //bin.set_remote_address( _ctrls[i]._addr );       //set remote address
                _ctrls[i]._limit -= bin.send( RSPS_SETTINGS._maxpacketsize );

                if( mss > 32 ) {
                    LOG(2) {
                        *TLOG << timefmt(RSP_SETTINGS.active_frame()) << " sending " << mss << " bytes to client " << i
                        << ", ackstate: " << timefmt(ctrl->_ackstateid)
                        << " at " << timefmt() << BINSTREAM_FLUSH;
                    }
                }
            }
        }

        _active_connector = 0;
    }

/*
    void get_managed_objects( rsp_view_container& dst )
    {
        //except classdef
        if( _vc._views.size() <= 1 )  return;

        uint n = _vc._views.size();
        for( uint i=1; i<n; ++i )
            dst.view_ins( _vc._views[i] );
    }
*/
    
    uint get_active_players() const                 { return _nplactive; }
    uint get_total_players() const                  { return _ctrls.size(); }


    rsp_s_object_state* view_ins( rsp_base* p )
    {
        return _vc.view_ins(p);
    }


public:
    rsp_s_connector* _active_connector;

protected:
    friend struct rsp_s_connector;


private:
    rsp_view_container<rsp_s_object_state> _vc;

    ///Slot for the connected client
    struct Slot
    {
        local<rsp_s_connector> _ctrl;
        //netAddress _addr;
        netstreamudpbuf _net;
        uint _trip_time;
        int _limit;

        Slot() {
            _trip_time = 0;
            _limit = 0;
        }
    };

    dynarray<Slot> _ctrls;                  ///< controllers for each remote client

    uint _nplactive;                        ///< active (connected now) players
    bool _reqdetachall;
};


#endif //__RSP_S_ROOM__HEADER_FILE__
